﻿<!DOCTYPE html>
<html>
<head>
<!--
  Copyright (c) 2015-2019 Jean-Marc VIGLINO, 
  released under CeCILL-B (french BSD like) licence: http://www.cecill.info/
-->
  <title>ol-ext: Flow style</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  
  <meta name="description" content="Draw flow maps or Sankey maps with Openlayers." />
  <meta name="keywords" content="ol, openlayers, vector, style, stroke, width, color, variable, sankey, flow, arrow" />

  <link rel="stylesheet" href="../style.css" />

  <!-- jQuery -->
  <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
  <!-- FontAwesome -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

  <!-- Openlayers -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@latest/ol.css" />
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/ol@latest/dist/ol.js"></script>

  
  <!-- ol-ext -->
  <link rel="stylesheet" href="../../dist/ol-ext.css" />
  <script type="text/javascript" src="../../dist/ol-ext.js"></script>
  <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
  <script src="https://unpkg.com/elm-pep"></script>

  <style>
    #map {
      width: 100%;
      min-height: 400px;
      height: 70vh;
    }
    .options label {
      display: inline-block;
      text-align: right;
      min-width: 4em;
    }
    .options input[type="number"] {
        width: 5em;
    }
  </style>
</head>
<body >
  <a href="https://github.com/Viglino/ol-ext" class="icss-github-corner"><i></i></a>

  <a href="../../index.html">
    <h1>ol-ext: Flow line style</h1>
  </a>
  <div class="info">
    <p>
      The <i>ol.style.FlowLine</i> is a line style to draw LineString with variable colors and widths.
      <br/>
      This can be used to display <a href="https://en.wikipedia.org/wiki/Flow_map">flows</a> maps
      or <a href="https://en.wikipedia.org/wiki/Sankey_diagram">Sankey diagram</a> on a map.
    </p>
    <ul>
    </ul>
    <p>
      Look at <a href='map.style.flowline.html'>this example</a> for more information on FlowLine style.
    </p>
  </div>

  <!-- Map div -->
  <div id="map"></div>

  <div class="options" style="min-width:300px;">
    <h2>Options:</h2>
    <ul>
			<li>
        <label>color:</label>
        <select class="color" onchange="vector.changed();">
          <option value='red' selected="selected">red</option>
          <option value='yellow'>yellow</option>
          <option value='#0f0'>green</option>
          <option value='#00f'>blue</option>
          <option value='#fff'>white</option>
          <option value='#000'>black</option>
          <option value='rgba(255,255,255,0)'>transparent</option>
        </select>
			</li>
			<li>
        <label>color2:</label>
        <select class="color2" onchange="vector.changed();">
          <option value='red'>red</option>
          <option value='yellow' selected="selected">yellow</option>
          <option value='#0f0'>green</option>
          <option value='#00f'>blue</option>
          <option value='#fff'>white</option>
          <option value='#000'>black</option>
          <option value='rgba(255,255,255,0)'>transparent</option>
        </select>
      </li>
      <li>
        <label>Offset:</label>
        <input class="offset" value="10" type="number" step=1 onchange="vector.changed();" />px
      </li>
      <li>
        <label>
          Curve lines : 
          <select id="curve" onchange="vector.changed();">
            <option value="0">none</option>
            <option value="curve">curve</option>
            <option value="circle">circle</option>
          </select>
        </label>
      </li>
		</ul>
  </div>
  
<script type="text/javascript">
  
  // Layers
  var layer = new ol.layer.Geoportail({ layer: 'ORTHOIMAGERY.ORTHOPHOTOS' });

  // The map
  var map = new ol.Map({
    target: 'map',
    view: new ol.View ({
      zoom: 7,
      center: [218664, 6158372]
    }),
    layers: [layer]
  });
  // GeoJSON layer
  var dep = new ol.layer.VectorImage ({ source: new ol.source.Vector({
      url: '../data/departements.geojson',
      format: new ol.format.GeoJSON(),
      attributions: [ "&copy; <a href='https://www.insee.fr'>INSEE</a>", "&copy; <a href='https://www.data.gouv.fr/fr/datasets/geofla-r/'>IGN</a>" ],
    })
  });
  map.addLayer(dep);

  // Read Data
  var flowData = {};
  // Event handlers when source is ready
	var listener = dep.getSource().on('change',function(e) {
    ol.Observable.unByKey(listener)
    if (dep.getSource().getState() === 'ready') {
      var sel;
      dep.getSource().getFeatures().forEach(function (f) {
        var p, g = f.getGeometry();
        if (f.get('id')==='41') sel = f;
        if (g.getInteriorPoint) {
          p = g.getInteriorPoint().getFirstCoordinate();
        } else {
          var max = 0;
          g.getPolygons().forEach(function(poly) {
            var a = poly.getArea();
            if (max<a) {
              max = a;
              p = poly.getInteriorPoint().getFirstCoordinate();
            }
          });
        }
        flowData[f.get('id')] = {
          xy: p,
          data: []
        };
      })
      $.ajax({
        url: '../data/mobilite-2017.csv',
        success: function (data) {
          data = data.split('\n');
          data.shift();
          data.forEach(function(l) {
            l = l.split(',');
            if (flowData[l[0]] && flowData[l[1]]) {
              flowData[l[0]].data.push ({
                dep: l[1],
                xy: flowData[l[1]].xy,
                flow: parseInt(l[2])
              });
            }
          })
        }
      });
      setTimeout(function() {
        select.dispatchEvent({ type:'select', selected:[sel] })
      }, 0);
		}
  });

  // Default style to make the feature selectable
  var defaultStyle = new ol.style.Style({
    stroke: new ol.style.Stroke({ color: [255, 255, 255, .1], width: 2.5 })
  });

  // Get geom
  function getGeom(f) {
    switch ($('#curve').val()) {
      case 'curve': {
        var l = f.getGeometry().getCoordinates();
        var p1 = l.pop();
        var p0 = l[0];
        var r = map.getView().getResolution();
        l.push([
          p0[0] + 8*(p1[0]-p0[0]) / 10, 
          p1[1] + (p1[1]-p0[1] < -30*r ? 3*Math.abs(p1[1]-p0[1])/4 : 40*r)
        ]);
        l.push(p1);
        var sp = ol.coordinate.cspline(l, {
          resolution: 500,
          tension: .75
        });
        return (new ol.geom.LineString(sp));
      }
      case 'circle': {
        var l = f.getGeometry().getCoordinates();
        var p1 = l.pop();
        var p0 = l[0];
        var dx = (p1[0] - p0[0]);
        var dy = (p1[1] - p0[1]);
        var a = Math.atan2(dy,dx);
        if (a < 0 || a > Math.PI) {
          dx=-dx;
          dy=-dy;
        }
        l.push([
          (p0[0]+p1[0])/2 + dy / 3, 
          (p0[1]+p1[1])/2 - dx / 3
        ]);
        l.push(p1);
        var sp = ol.coordinate.cspline(l, {
          resolution: 500,
          tension: .75
        });
        return (new ol.geom.LineString(sp));
      }
      default: {
        return f.getGeometry();
      }
    }
  }

  // Flow style
  var done = false;
  function getStyle(feature, res) {
    var flowStyle = new ol.style.FlowLine({
      color: $('.color').val(),
      color2: $('.color2').val(),
      width: 2,
      width2: feature.get('flow'),
      offset0: parseInt($('.offset').val()),
      arrow: 1,
      geometry: getGeom,
      zIndex: -(feature.getGeometry().getLastCoordinate()[1])
    });
    return [ defaultStyle, flowStyle ];
  }

  // Nouvelle source de donnee
  var vector = new ol.layer.VectorImage({
    source: new ol.source.Vector(),
    style: getStyle
  })
  map.addLayer(vector);

  var popup = new ol.Overlay.Popup({ className: 'tooltips' });
  map.addOverlay(popup);

  var hover = new ol.interaction.Hover({ cursor: 'pointer', layers: [vector], hitTolerance:2 });
  map.addInteraction(hover);
  hover.on('hover', function(e) {
    popup.show(e.coordinate, e.feature.get('flux'));
  });
  hover.on('leave', function(e) {
    popup.hide();
  });

  // global so we can remove it later
  var select = new ol.interaction.Select ({ 
    condition: ol.events.condition.Click,
    layers: [dep] 
  });
  map.addInteraction(select);
  select.on('select', function(e) {
    var f = e.selected[0];
    vector.getSource().clear();
    if (f) {
      var id = f.get('id');
      var dep = flowData[id]
      dep.data.forEach(function(d) {
        if (d.flow > 300 && d.dep !== id) {
          var l = new ol.Feature(new ol.geom.LineString([dep.xy, d.xy]))
          l.set('flow', Math.max(2, Math.min(30, d.flow/300)));
          l.set('flux', d.flow.toLocaleString());
          vector.getSource().addFeature(l);
        }
      });
    }
  });

</script>

</body>
</html>